/* Darren Mui - CS362 */

#include "dominion.h"
#include "dominion_helpers.h"
#include <string.h>
#include <stdio.h>
#include <assert.h>
#include "rngs.h"

#define NOISY_TEST 1
#define ANTI_LOOP_MAX 10000

char *printCard (int card) {
	switch (card) {
		case curse:
			return "Curse";
		case estate:
			return "Estate";
		case duchy:
			return "Duchy";
		case province:
			return "Province";
		case copper:
			return "Copper";
		case silver:
			return "Silver";
		case gold:
			return "Gold";
		case adventurer:
			return "Adventurer";
		case council_room:
			return "Council Room";
		case feast:
			return "Feast";
		case gardens:
			return "Gardens";
		case mine:
			return "Mine";
		case remodel:
			return "Remodel";
		case smithy:
			return "Smithy";
		case village:
			return "Village";
		case baron:
			return "Baron";
		case great_hall:
			return "Great Hall";
		case minion:
			return "Minion";
		case steward:
			return "Steward";
		case tribute:
			return "Tribute";
		case ambassador:
			return "Ambassador";
		case cutpurse:
			return "Cutpurse";
		case embargo:
			return "Embargo";
		case outpost:
			return "Outpost";
		case salvager:
			return "Salvager";
		case sea_hag:
			return "Sea Hag";
		case treasure_map:
			return "Treasure Map";
	}
	return NULL;
}

/* output current supply count to gameResults.out */
void printSupply (FILE * outputFile, struct gameState *state) {
	char *s;
	int i;

	fprintf(outputFile, "SUPPLY PILES:\n");
	// outputs count of all valid supply piles
	for (i = 0; i < treasure_map + 1; i++) {
		if (supplyCount(i, state) > -1) { // if a valid supply pile
			s = printCard(i);
			fprintf(outputFile, "%s - Supply Count: %d\n", s, supplyCount(i, state));
		}
	}
	fprintf(outputFile, "\n");
}

// outputs information about current player to gameResults.out
void printState (FILE * outputFile, struct gameState *state) {
	char *s;
	int i, p;
	int h;
	int deckTotal, discardTotal, handTotal;
	int actionCount, buyCount, coinCount;

	p = state->whoseTurn;
	deckTotal = state->deckCount[p];
	discardTotal = state->discardCount[p];
	handTotal = state->handCount[p];
	actionCount = state->numActions;
	buyCount = state->numBuys;
	coinCount = state->coins;

	fprintf(outputFile, "PLAYER %d HAS %d CARDS IN DECK, %d IN DISCARD, AND %d IN HAND\n", p, deckTotal, discardTotal, handTotal);
	fprintf(outputFile, "PLAYER %d HAS %d ACTIONS, %d BUYS AND %d COINS\n", p, actionCount, buyCount, coinCount);
	fprintf(outputFile, "PLAYER %d's HAND:\n", p);
	for (i = 0; i < numHandCards(state); i++) {
		h = handCard(i, state);
		s = printCard(h);
		fprintf(outputFile, "%d. %s\n", i, s);
	}

	fprintf(outputFile, "\n");
}

/*	plays as many action cards as possible (based on number of action cards in hand & number of
	action points), will also make random (but proper) choices for the action cards that require
	them. */
void playAction (FILE * outputFile, struct gameState *state) {
	int i, j;
	int c1, c2, c3; // choices
	int x, y, p, h;
	char *s;
	int antiLoop;
	int actionCardsCount; // keep track of number of action cards in hand
	int handTotal, actionCount;
	int supplyCheck, supplyCost1, supplyCost2;

	actionCardsCount = 0;
	p = state->whoseTurn;
	handTotal = state->handCount[p];
	actionCount = state->numActions;

	for (i = 0; i < handTotal; i++) {
		h = state->hand[p][i];
		if (h >= adventurer && h <= treasure_map && h != gardens) actionCardsCount++;
	}

	// go through player's hand and play as many action cards as possible
	while (actionCount > 0 && actionCardsCount > 0) {
		for (i = 0 ; i < handTotal; i++) {
			antiLoop = 0; // for breaking out potential infinite loop situations in the test
			c1 = -1;
			c2 = -1;
			c3 = -1;
			y = 0;
			// play action card
			h = state->hand[p][i];
			if (h >= adventurer && h <= treasure_map && h != gardens) {
				// check if card needs to have choices made
				switch (h) {
					case feast: // randomize what card to gain, but make sure there's enough supply & costs at most 5
						do {
							c1 = floor(Random() * (treasure_map + 1));
							supplyCheck = state->supplyCount[c1];
							supplyCost1 = getCost(c1);
							antiLoop++;
							if (antiLoop >= ANTI_LOOP_MAX) break; // loop 10k times before breaking as there is most likely no supply left that player can gain
						} while (supplyCheck < 1 || supplyCost1 > 5);
						break;

					case mine: // randomize what treasure card to trash and gain
						do {
							c1 = floor(Random() * (handTotal + 1));
							supplyCost1 = getCost(state->hand[p][c1]);
							antiLoop++;
							if (antiLoop >= ANTI_LOOP_MAX) break;
						} while (supplyCost1 < copper || supplyCost1 > gold); // make sure it's a treasure card
						antiLoop = 0;
						do {
							c2 = floor(Random() * (province) + 4); // randomize from copper to gold
							supplyCheck = state->supplyCount[c2];
							supplyCost2 = getCost(c2);
							antiLoop++;
							if (antiLoop >= ANTI_LOOP_MAX) break;
						} while (supplyCheck < 1 || supplyCost2 > (supplyCost1 + 3));
						break;

					case remodel:
						do {
							c1 = floor(Random() * (handTotal + 1));
							antiLoop++;
							if (antiLoop >= ANTI_LOOP_MAX) break;
						} while (c1 == i); // prevent trashing the card that is being played
						antiLoop = 0;
						do {
							c2 = floor(Random() * (treasure_map + 1));
							supplyCheck = state->supplyCount[c2];
							supplyCost1 = getCost(state->hand[p][c1]);
							supplyCost2 = getCost(c2);
							antiLoop++;
							if (antiLoop >= ANTI_LOOP_MAX) break;
						} while (supplyCheck < 1 || supplyCost2 > (supplyCost1 + 2));
						break;

					case baron:
						c1 = floor(Random() * 2); // randomize from 0 to 1
						break;

					case minion:
						x = floor(Random() * 2); // randomize from 0 to 1
						if (x == 0) c1 = 1; // +2 coins
						else if (x == 1) c2 = 1; // discard hand, draw 4, etc...
						break;

					case steward:
						/*	if player has enough cards in hand to play Steward AND trash 2 cards, then randomize
							from the three possible choices, otherwise only randomize from the other two choices */
						if (handTotal >= 3)
							x = floor(Random() * 3); // randomize from 0 to 2
						else
							x = floor(Random() * 2); // randomize from 0 to 1

						if (x == 0) c1 = 1;
						else if (x == 1) c1 = 2;
						/*	trash 2 cards from hand, make sure not trashing same card twice
							and neither of the two cards we are trashing is the playing Steward card itself */
						else if (x == 2) {
							do {
								c2 = floor(Random() * (handTotal + 1));
								c3 = floor(Random() * (handTotal + 1));
								antiLoop++;
								if (antiLoop >= ANTI_LOOP_MAX) break;
							} while (c2 == c3 || c2 == i || c3 == i); 
						}
						break;

					case ambassador:
						do {
							c1 = floor(Random() * (handTotal + 1));
							c2 = floor(Random() * 3); // randomize from 0 to 2
							for (j = 0; j < handTotal; j++) {
								if (state->hand[p][j] == state->hand[p][c1] && j != c1) 
									y++;
							}
							antiLoop++;
							if (antiLoop >= ANTI_LOOP_MAX) break;
						} while (c1 == i || c2 > y); // make sure enough cards to return
						break;

					case embargo: // randomize what supply pile to put embargo token
						do {

							c1 = floor(Random() * (treasure_map + 1));
							supplyCheck = state->supplyCount[c1];
							antiLoop++;
							if (antiLoop >= ANTI_LOOP_MAX) break;
						} while (supplyCheck == -1); // make sure it's a valid supply pile
						break;

					case salvager:
						do {
							antiLoop++;
							c1 = floor(Random() * (handTotal + 1));
							if (antiLoop >= ANTI_LOOP_MAX) break;
						} while (c1 == i);
						break;

				}
				/*	only play the card if it didn't get stuck in a loop for ANTI_LOOP_MAX times, else discard the
					card and act like we played it (without calling playCard). This is done to avoid certain cases
					such as the infinite loop in Feast when playing it and it turning out that we can't afford any
					of the supply cards. */
				s = printCard(h);
				if (antiLoop >= ANTI_LOOP_MAX) {
					discardCard(i, p, state, 0);
					fprintf(outputFile, "PLAYER %d PLAYED %s FROM HAND POSITION %d WITH INVALID CHOICES, CARD DISCARDED\n\n", p, s, i);
					state->numActions--;
				}
				else {
					if (playCard(i, c1, c2, c3, state) != -1) {
						fprintf(outputFile, "PLAYER %d PLAYED %s FROM HAND POSITION %d WITH CHOICES: %d, %d, %d\n\n", p, s, i, c1, c2, c3);
					}
				}
				printState(outputFile, state);
				actionCardsCount--;
			}
			handTotal = state->handCount[p];
			actionCount = state->numActions;
		}
	}
}

/*	buy as many cards from random valid supply piles as the player can given the number of
	of buys and coins. */
void playBuy (FILE * outputFile, struct gameState *state) {
	int x, supp, antiLoop;
	antiLoop = 0;
	int coinCount, buyCount;
	int p;
	char *s;

	p = whoseTurn(state);

	coinCount = state->coins;
	buyCount = state->numBuys;

	while (coinCount > 0 && buyCount > 0) {
		do {
			supp = floor(Random() * (treasure_map + 1));
			antiLoop++;
			if (antiLoop >= ANTI_LOOP_MAX) break;
		} while (supplyCount(supp, state) < 1 || getCost(supp) > state->coins);
		x = buyCard(supp, state);
		s = printCard(supp);
		// if card is successfully bought, output results to file
		if (x == 0) { 
			fprintf(outputFile, "PLAYER %d BOUGHT %s\n\n", p, s);
			printState(outputFile, state);
		}

		coinCount = state->coins;
		buyCount = state->numBuys;

		antiLoop++;
		if (antiLoop >= ANTI_LOOP_MAX) break;
	}

	fprintf(outputFile, "\n");
}

int main () {
	SelectStream(2);
	PutSeed(3); // use negative number for seed based on system clock
	struct gameState G;
	int k[10];
	int i, j, p;

	//set up to output game results
	FILE * outputFile;
	outputFile = fopen("gameResults.out", "w+");

	//randomize the 10 kingdom cards from adventurer to treasure_map
	for (i = 0; i < 10; i++) {
		k[i] = floor( (Random() * (treasure_map - adventurer + 1)) + adventurer);
	}	
	//randomize duplicates until there are no more duplicate kingdom cards
	for (i = 0; i < 10; i++) {
		for (j = 0; j < 10; j++) {
			while (k[i] == k[j] && i != j) {
				k[i] = floor( (Random() * (treasure_map - adventurer + 1)) + adventurer);
				j = 0;
			}
		}
	}

	//randomize p from 2 to 4 (for number of players)
	p = floor((Random() * 3) + 2);
	initializeGame(p, k, floor(Random() * 256), &G);
	fprintf(outputFile, "GAME INITIALIZED WITH %d PLAYERS\n", G.numPlayers);

	int antiLoop;
	antiLoop = 0;

	//PLAY THE GAME
	while (isGameOver(&G) != 1) {
		printSupply(outputFile, &G);
		fprintf(outputFile, "PLAYER %d's TURN\n", G.whoseTurn);
		printState(outputFile, &G);
		playAction(outputFile, &G); // action phase
		playBuy(outputFile, &G); // buy phase
		fprintf(outputFile, "END PLAYER %d's TURN\n\n", G.whoseTurn);
		fprintf(outputFile, "----------------------------------------------------------\n");
		endTurn(&G); // clean up phase
		antiLoop++;
		if (antiLoop >= ANTI_LOOP_MAX) break;
	}

	fprintf(outputFile, "GAME IS OVER\n");
	for (i = 0; i < G.numPlayers; i++) {
		fprintf(outputFile, "Player %d's Score: %d\n", i, scoreFor(i, &G));
	}
	printf("TEST GAME COMPLETE\n");

	fclose(outputFile);
	return 0;
}


